package com.molichuxing.services.infrastructure.service.impl;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSONObject;
import com.molichuxing.framework.exception.BizException;
import com.molichuxing.framework.utils.DateUtil;
import com.molichuxing.services.infrastructure.bean.EscrowLogBean;
import com.molichuxing.services.infrastructure.bean.EscrowNotifyLogBean;
import com.molichuxing.services.infrastructure.dao.EscrowLogMapper;
import com.molichuxing.services.infrastructure.dao.EscrowNotifyLogMapper;
import com.molichuxing.services.infrastructure.dto.request.create.WechatpayCreateDto;
import com.molichuxing.services.infrastructure.dto.response.PrepaymentDto;
import com.molichuxing.services.infrastructure.dto.response.WechatpayResultDto;
import com.molichuxing.services.infrastructure.service.WechatpayService;
import com.molichuxing.services.property.EscrowLogTypeEnum;
import com.molichuxing.services.property.EscrowNotifyLogTypeEnum;
import com.molichuxing.services.property.PaymentChannelEnum;
import com.molichuxing.services.property.sdk.WechatpayTradeStateEnum;
import com.molichuxing.services.property.sdk.WechatpayTradeTypeEnum;
import com.molichuxing.services.sdk.wxpay.WXPay;
import com.molichuxing.services.sdk.wxpay.WXPayConfigImpl;
import com.molichuxing.services.sdk.wxpay.WXPayConstants;
import com.molichuxing.services.utils.WechatpayUtil;

@Service("wechatpayService")
public class WechatpayServiceImpl implements WechatpayService {
	private static final Logger logger = LoggerFactory.getLogger(WechatpayServiceImpl.class);

	@Resource
	private EscrowLogMapper escrowLogMapper;

	@Resource
	private EscrowNotifyLogMapper escrowNotifyLogMapper;

	/**
	 * 统一下单(预支付单)
	 * 
	 * @param createDto
	 * @return
	 * @throws Exception
	 */
	@Override
	public PrepaymentDto orderPay(WechatpayCreateDto createDto) throws Exception {
		// 获取配置
		WXPayConfigImpl config = WechatpayUtil.getConfig(createDto.getTradeType());
		// 微信支付sdk
		WXPay wxPay = new WXPay(config, config.getPayNotify(), config.getUseSandbox());
		// 生成请求参数
		Map<String, String> reqData = WechatpayUtil.orderPayParams(createDto);
		// 调用微信统一下单接口
		Map<String, String> respData = wxPay.unifiedOrder(reqData);
		// 保存请求参数、响应参数
		saveEscrowLog(reqData, respData, EscrowLogTypeEnum.ORDER_PAY);
		
		// 判断响应结果
		if (!WXPayConstants.SUCCESS.equals(respData.get("return_code"))
				|| !WXPayConstants.SUCCESS.equals(respData.get("result_code"))) {
			// 失败
			logger.error("[orderPay]订单(订单号:{})调用微信生成预支付单失败：{}", createDto.getProductId(), respData.toString());
			throw new BizException("微信生成预支付单失败");
		}
		// 生成回显参数
		return WechatpayUtil.orderPayResponseParams(config, respData, reqData.get("out_trade_no"));
	}

	/**
	 * 查询订单
	 * 
	 * @param outTradeNo
	 *            商户订单号
	 * @param tradeType
	 *            交易类型
	 * @return
	 * @throws Exception
	 */
	@Override
	public WechatpayResultDto orderQuery(String outTradeNo, WechatpayTradeTypeEnum tradeType) throws Exception {
		WechatpayResultDto result = null;
		// 获取配置
		WXPayConfigImpl config = WechatpayUtil.getConfig(tradeType);
		// 微信支付sdk
		WXPay wxPay = new WXPay(config, config.getPayNotify(), config.getUseSandbox());
		// 查询参数
		Map<String, String> reqData = new HashMap<>();
		// 商户订单号
		reqData.put("out_trade_no", outTradeNo);
		// 调用微信查询订单接口
		Map<String, String> respData = wxPay.orderQuery(reqData);
		// 保存请求参数、响应参数
		saveEscrowLog(reqData, respData, EscrowLogTypeEnum.ORDER_QUERY);

		// 判断响应结果
		if (!WXPayConstants.SUCCESS.equals(respData.get("return_code"))
				|| !WXPayConstants.SUCCESS.equals(respData.get("result_code"))) {
			// 失败
			logger.error("[orderQuery]支付单(商户订单号:{})调用微信查询订单失败：{}", outTradeNo, respData.toString());
			throw new BizException("微信查询订单失败");
		}
		result = new WechatpayResultDto();
		result.setOutTradeNo(outTradeNo);
		result.setTransactionId(respData.get("transaction_id"));
		result.setTradeState(WechatpayTradeStateEnum.getEnum(respData.get("trade_state")));
		if (WXPayConstants.SUCCESS.equals(respData.get("trade_state"))) {
			result.setTotalFee(new BigDecimal(respData.get("total_fee")));
			result.setTimeEnd(DateUtil.parseDateTime(respData.get("time_end"), "yyyyMMddHHmmss"));
			result.setAttach(respData.get("attach"));
		}

		return result;
	}

	/**
	 * 关闭订单
	 * 
	 * @param outTradeNo
	 *            商户订单号
	 * @param tradeType
	 *            交易类型
	 * @return
	 * @throws Exception
	 */
	@Override
	public Boolean orderClose(String outTradeNo, WechatpayTradeTypeEnum tradeType) throws Exception {
		// TODO 先查询订单

		// 获取配置
		WXPayConfigImpl config = WechatpayUtil.getConfig(tradeType);
		// 微信支付sdk
		WXPay wxPay = new WXPay(config, config.getPayNotify(), config.getUseSandbox());
		// 查询参数
		Map<String, String> reqData = new HashMap<>();
		// 商户订单号
		reqData.put("out_trade_no", outTradeNo);
		// 调用微信关闭订单接口
		Map<String, String> respData = wxPay.closeOrder(reqData);
		// 保存请求参数、响应参数
		saveEscrowLog(reqData, respData, EscrowLogTypeEnum.ORDER_CLOSE);

		// 判断响应结果
		if (!WXPayConstants.SUCCESS.equals(respData.get("return_code"))
				|| !WXPayConstants.SUCCESS.equals(respData.get("result_code"))) {
			// 失败
			logger.error("[orderClose]支付单(商户订单号:{})调用微信关闭订单失败：{}", outTradeNo, respData.toString());
			return false;
		}
		return true;
	}

	/**
	 * 微信支付结果异步通知解析
	 * 
	 * @param notifyStr
	 *            通知信息
	 * @param tradeType
	 *            交易类型
	 * @return
	 * @throws Exception
	 */
	@Override
	public WechatpayResultDto notifyPay(String notifyStr, WechatpayTradeTypeEnum tradeType) throws Exception {
		WechatpayResultDto result = null;
		// 获取配置
		WXPayConfigImpl config = WechatpayUtil.getConfig(tradeType);
		// 微信支付sdk
		WXPay wxPay = new WXPay(config, config.getPayNotify(), config.getUseSandbox());
		// 处理 HTTPS API返回数据，转换成Map对象。return_code为SUCCESS时，验证签名。
		Map<String, String> notifyData = wxPay.processResponseXml(notifyStr);
		// 判断结果
		if (!WXPayConstants.SUCCESS.equals(notifyData.get("return_code"))) {
			logger.error("[notifyPay]微信支付回调失败：{}", notifyData.toString());
			return result;
		}
		// 保存回调日志
		saveNotifyLog(notifyData, EscrowNotifyLogTypeEnum.PAY);

		result = new WechatpayResultDto();
		result.setOutTradeNo(notifyData.get("out_trade_no"));
		result.setTransactionId(notifyData.get("transaction_id"));
		result.setTotalFee(new BigDecimal(notifyData.get("total_fee")));
		if (WXPayConstants.SUCCESS.equals(notifyData.get("result_code"))) {
			result.setTradeState(WechatpayTradeStateEnum.SUCCESS);
			result.setTimeEnd(DateUtil.parseDateTime(notifyData.get("time_end"), "yyyyMMddHHmmss"));
		} else {
			logger.error("[notifyPay]微信支付回调失败：{}", notifyData.toString());
			result.setTradeState(WechatpayTradeStateEnum.PAYERROR);
		}
		result.setAttach(notifyData.get("attach"));
		return result;
	}

	/**
	 * 保存请求日志
	 * 
	 * @param reqData
	 * @param respData
	 */
	@Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class)
	public void saveEscrowLog(Map<String, String> reqData, Map<String, String> respData, EscrowLogTypeEnum type) {
		EscrowLogBean escrowLogBean = new EscrowLogBean();
		escrowLogBean.setPayNumber(reqData.get("out_trade_no"));
		escrowLogBean.setRequestData(JSONObject.toJSONString(reqData));
		escrowLogBean.setResponseData(JSONObject.toJSONString(respData));
		escrowLogBean.setChannel(PaymentChannelEnum.WXPAY.getValue());
		escrowLogBean.setType(type.getValue());
		escrowLogBean.setRemark(type.getName());
		escrowLogMapper.create(escrowLogBean);
	}

	/**
	 * 保存回调日志
	 * 
	 * @param notifyData
	 * @param type
	 */
	@Transactional(propagation = Propagation.NOT_SUPPORTED, isolation = Isolation.DEFAULT, timeout = 36000, rollbackFor = Exception.class)
	public void saveNotifyLog(Map<String, String> notifyData, EscrowNotifyLogTypeEnum type) {
		try {
			EscrowNotifyLogBean escrowNotifyLogBean = new EscrowNotifyLogBean();
			escrowNotifyLogBean.setPayNumber(notifyData.get("out_trade_no"));
			escrowNotifyLogBean.setChannel(PaymentChannelEnum.WXPAY.getValue());
			escrowNotifyLogBean.setType(type.getValue());
			escrowNotifyLogBean.setNotifyData(JSONObject.toJSONString(notifyData));
			escrowNotifyLogMapper.create(escrowNotifyLogBean);
		} catch (Exception e) {
			logger.error("[saveNotifyLog]微信({}:{})回调保存日志异常", type.getValue(), type.getName());
		}
	}
}
